package biback.domain.common

import cats.Monad
import biback.domain._
import biback.domain.common.model.{ChannelType, Currency}
import biback.utils._

trait BankAlgebra[F[_]] {
  val bank: BankAlgebra.Service[F]
}

object BankAlgebra {

  trait Service[F[_]] extends GeneralAlgebra[F] {
    def getTrnDate(implicit F: Monad[F]): ErrOrF[TrnDate] =
      orF(Left(MethodNoImplement("getTrnDate")))
    def isLocalCcy(ccy: Currency)(implicit F: Monad[F]): ErrOrF[Boolean] =
      orF(Left(MethodNoImplement("isLocalCcy")))
  }

}

class BankService[F[_] : Monad](algebra: BankAlgebra[F] with BranchAlgebra[F]) extends GeneralAlgebra[F] {

  import algebra.branch._
  import algebra.bank._

  def getQuotaDuty(trn: TrnInitiation, duty: TrnDuty): ErrOrF[TrnDuty] =
    (trn.quotaBranch, trn.quotaTeller) match {
      case (Some(brc), Some(tlr)) =>
        foundTeller(tlr).flatMap(t => orF(t.branch == brc, TrnDuty(brc, Some(tlr)), TellerBranchNoMatch(tlr, brc)))
      case (Some(brc), None) =>
        resultF(TrnDuty(brc))
      case (None, Some(tlr)) =>
        foundTeller(tlr).map(t => TrnDuty(t.branch, Some(tlr)))
      case (None, None) =>
        resultF(TrnDuty(duty.brcNo, duty.tlrNo))
    }

  def getTrnDuty(trn: TrnInitiation): ErrOrF[TrnDuty] =
    trn.invoker match {
      case Some(tlr) =>
        foundTeller(tlr).map(t => TrnDuty(t.branch, Some(tlr)))
      case None =>
        foundBranchNo(trn.channel).map(brc => TrnDuty(brc))
    }

  def contextOf(trn: TrnInitiation): ErrOrF[TrnContext] =
    for {
      duty <- getTrnDuty(trn)
      quota <- getQuotaDuty(trn, duty)
      date <- getTrnDate
    } yield TrnContext(trn.id, trn.channel, trn.trnCode, date, duty, quota)
}

case class TrnContext(
                       id: TrnId,
                       channel: ChannelType,
                       trnCode: TrnCode,
                       date: TrnDate,
                       oper: TrnDuty,
                       quota: TrnDuty
                     )

case class TrnInitiation(
                      id: TrnId,
                      channel: ChannelType,
                      trnCode: TrnCode,
                      invoker: Option[TellerNo] = None,
                      quotaTeller: Option[TellerNo] = None,
                      quotaBranch: Option[BranchNo] = None
                    )

final case class TrnDuty(brcNo: BranchNo, tlrNo: Option[TellerNo] = None)

